/*
 * @Author: liutongchao 1923236391@qq.com
 * @Date: 2023-05-29 11:54:12
 * @LastEditors: liutongchao 1923236391@qq.com
 * @LastEditTime: 2025-03-27 14:30:50
 * @FilePath: \greenbeeadmin\controllers\greenbee\upload.go
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
package controllers

import (
	"database/sql"
	"encoding/json"
	greenbeeUtils "greenbeeadmin/controllers/greenbee/utils"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	"github.com/beego/beego/v2/client/orm"

	"bufio"
	"fmt"
	"html"

	systemConfig "github.com/beego/beego/v2/core/config"

	_ "github.com/go-sql-driver/mysql" // import your used driver
)

// 系统初始化安装 controller
type CmfInstallController struct {
	MainBaseController
}

// 系统安装成功 controller
type CmfInstallSuccessController struct {
	MainBaseController
}

// 系统安装判定文件地址
const LOCK_FILE_PATH = "conf/install.lock"

// 系统安装成功页面
func (c *CmfInstallSuccessController) Get() {
	step := "5"
	step_html := "完成"

	//判断系统安装状态
	//file_path := "conf/install.lock"
	is_exist := greenbeeUtils.FileExists(LOCK_FILE_PATH)
	if !is_exist {
		//您已安装过系统，跳转页面
		// 跳转登录页,方式：301(永久移动),308(永久重定向),307(临时重定向)
		c.Redirect("/install?step=1", 302)
	}
	c.Data["install_name"] = "GreenBeeAdmin "
	c.Data["version"] = "1.0.0"
	c.Data["step_html"] = step_html
	c.Data["footer_info"] = "@GreenBeeAdmin 版权所有 "
	c.Data["title"] = "欢迎使用 GreenBeeAdmin 后台管理系统"
	c.Data["descript"] = "这是一个高效、易用的管理平台，旨在帮助程序员提高开发效率。GreenBeeAdmin 为您提供了直观的操作界面和强大的RBAC功能支持，让您能够快速上手并高效完成任务。我们致力于为您提供稳定、安全的服务。如有任何问题或建议，请随时在我们的gitee提交issues，我们将尽快为您解答问题！"
	c.TplName = "common/install/" + step + ".html"
}

// 系统安装页面
func (c *CmfInstallController) Get() {

	//判断系统安装状态
	//file_path := "conf/install.lock"
	is_exist := greenbeeUtils.FileExists(LOCK_FILE_PATH)
	if is_exist {
		//您已安装过系统，跳转页面
		// 跳转登录页,方式：301(永久移动),308(永久重定向),307(临时重定向)
		c.Redirect("/installSuccess", 302)
	}

	//go版本判断是否大于1.19+
	go_version := runtime.Version()
	c.Data["go_version_check"] = 0
	go_version_num := strings.Replace(go_version, "go", "", -1)
	go_version_arr := strings.Split(go_version_num, ".")
	if len(go_version_arr) > 1 {
		v1, _ := strconv.Atoi(go_version_arr[0])
		v2, _ := strconv.Atoi(go_version_arr[1])
		if v1 >= 1 && v2 >= 19 {
			c.Data["go_version_check"] = 1
		}
	}

	step := c.GetString("step", "1")
	config_params := c.GetString("config_params", "")
	step_html := "第1步"
	if step == "1" {
		step_html = "第1步"
	} else if step == "2" {
		step_html = "第2步"
	} else if step == "3" {
		//判断go版本是否大于1.19+
		if c.Data["go_version_check"] != 1 {
			// 跳转登录页,方式：301(永久移动),308(永久重定向),307(临时重定向)
			c.Redirect("/install?step=2", 302)
		}
		step_html = "第3步"
	} else if step == "4" {
		//判断go版本是否大于1.19+
		if c.Data["go_version_check"] != 1 {
			// 跳转登录页,方式：301(永久移动),308(永久重定向),307(临时重定向)
			c.Redirect("/install?step=2", 302)
		}

		if config_params == "" {
			// 跳转登录页,方式：301(永久移动),308(永久重定向),307(临时重定向)
			c.Redirect("/install?step=3", 302)
		}

		step_html = "第4步"
	}
	//获取env信息
	go_env := os.Environ()

	c.Data["install_name"] = "GreenBeeAdmin "
	c.Data["version"] = "1.0.0"
	c.Data["step_html"] = step_html
	c.Data["footer_info"] = "@GreenBeeAdmin 版权所有 "
	c.Data["license"] = c.GetListens()
	c.Data["go_env"] = go_env

	c.Data["os"] = runtime.GOOS
	c.Data["go_version"] = go_version
	c.Data["config_params"] = config_params

	c.TplName = "common/install/" + step + ".html"
}

// 系统配置文件结构体
type SysConfigStruct struct {
	ConfigMap map[string]string `json:"config_map"`
}

// 系统安装方法
func (c *CmfInstallController) Post() {
	var data interface{}
	show_data := ""
	action := c.GetString("action", "")

	//接收数据库配置参数
	db_username := c.GetString("dbuser", "")
	db_password := c.GetString("dbpw", "")
	db_host := c.GetString("dbhost", "")
	db_port := c.GetString("dbport", "")

	//测试数据库链接
	if action == "test_db" {

		// 数据库连接字符串，格式为：用户名:密码@tcp(地址:端口)/数据库名
		dsn := db_username + ":" + db_password + "@tcp(" + db_host + ":" + db_port + ")/mysql"
		db, err := sql.Open("mysql", dsn)
		if err != nil {
			c.ErrorJson(0, "数据库连接失败:"+err.Error(), 0, show_data)
		}
		defer db.Close()

		// 查询版本号
		var version string
		err = db.QueryRow("SELECT VERSION()").Scan(&version)
		if err != nil {
			c.ErrorJson(0, "数据库连接失败2:"+err.Error(), 0, show_data)
		}
		//fmt.Printf("MySQL Version: %s\n", version)
		//判断msql版本是否大于5.6
		mysql_version_arr := strings.Split(version, ".")
		if len(mysql_version_arr) > 1 {
			v1, _ := strconv.Atoi(mysql_version_arr[0])
			v2, _ := strconv.Atoi(mysql_version_arr[1])
			if v1 >= 5 && v2 >= 6 {
				//c.SuccessJson(200, data, 0)
				msg := "数据库连接成功！"

				if v2 > 6 {
					show_data = "当前使用的 MySQL 版本是 " + version + "。如果你用的是 5.7 或更高版本，需要在配置文件中移除 ONLY_FULL_GROUP_BY 这个参数。"
				}
				c.ErrorJson(200, msg, 0, show_data)
			} else {
				c.ErrorJson(0, "数据库版本过低，请升级到5.6及以上版本！", 0, show_data)
			}
		}
		c.ErrorJson(0, "数据库连接失败!", 0, show_data)
	} else if action == "install" {
		//创建数据库，更新数据库的配置信息
		db_name := c.GetString("dbname", "")
		db_charset := "utf8"
		sitename := c.GetString("sitename", "")
		siteurl := c.GetString("siteurl", "")
		//sitedescription := c.GetString("sitedescription", "")
		manager := c.GetString("manager", "")
		J_manager_pwd := c.GetString("J_manager_pwd", "")
		system_key := c.GetString("system_key", "")
		//db_driver := "mysql"
		var config_map SysConfigStruct
		if db_name == "" {
			c.ErrorJson(0, "数据库名称不能为空！", 0, show_data)
		}

		dsn := db_username + ":" + db_password + "@tcp(" + db_host + ":" + db_port + ")/"
		db, err := sql.Open("mysql", dsn)
		if err != nil {
			//log.Fatal(err)
			c.ErrorJson(0, "数据库连接失败："+err.Error(), 0, show_data)

		}
		defer db.Close()

		// 创建数据库 CREATE DATABASE mydatabase CHARACTER SET utf8 COLLATE utf8_general_ci;
		_, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + db_name + " CHARACTER SET " + db_charset + " COLLATE utf8_general_ci")
		if err != nil {
			//log.Fatal(err)
			c.ErrorJson(0, "数据库创建失败："+err.Error(), 0, show_data)
		}

		//配置文件更新数据库配置信息
		modify_map := make(map[string]string)

		modify_map["db_host"] = db_host
		modify_map["db_username"] = db_username
		modify_map["db_password"] = db_password
		modify_map["db_port"] = db_port
		modify_map["db_name"] = db_name

		if len(system_key) == 16 {
			modify_map["greenbee_system_key"] = system_key
		}

		//添加网站配置信息
		modify_map["sitename"] = sitename
		modify_map["siteurl"] = siteurl
		modify_map["manager"] = manager
		modify_map["manager_pwd"] = J_manager_pwd

		//标记为已安装
		modify_map["is_install"] = "true"

		//更新全局配置信息
		for kk, vv := range modify_map {
			systemConfig.Set(kk, vv)
		}

		err = c.ModifyConfig("conf/app.conf", modify_map) // 修改配置文件
		if err != nil {
			//fmt.Println("Error modifying config:", err)
			c.ErrorJson(0, "数据库配置文件更新失败:"+err.Error(), 0, show_data)
		}
		config_map.ConfigMap = modify_map
		config_map.ConfigMap["db_charset"] = db_charset
		config_map.ConfigMap["db_driver"] = "mysql"
		//json序列化字符串
		bytes, err := json.Marshal(config_map)
		if err == nil {
			show_data = string(bytes)
		}
		c.ErrorJson(200, "数据库创建成功", 0, show_data)

	} else if action == "create_data" {
		//创建数据表，插入数据
		n, _ := c.GetInt("n", 0)
		sys_config := c.GetString("sys_config", "")

		//system配置参数验证
		var config_map SysConfigStruct
		if sys_config == "" {
			c.ErrorJson(0, "配置参数不存在，请返回上一步！", 0, show_data)
		}
		err := json.Unmarshal([]byte(sys_config), &config_map)
		if err != nil || len(config_map.ConfigMap) == 0 {
			c.ErrorJson(0, "配置参数解析失败，请返回上一步！", 0, show_data)
		}
		config := config_map.ConfigMap
		config_db_host, ok1 := config["db_host"]
		config_db_username, ok2 := config["db_username"]
		config_db_password, ok3 := config["db_password"]
		config_db_port, ok4 := config["db_port"]
		config_db_name, ok5 := config["db_name"]
		config_manager, ok6 := config["manager"]
		config_manager_pwd, ok7 := config["manager_pwd"]
		config_sitename, ok8 := config["sitename"]
		config_siteurl, ok9 := config["siteurl"]

		if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 || !ok7 || !ok8 || !ok9 {
			c.ErrorJson(0, "配置参数解析失败，请返回上一步！", 0, show_data)
		}

		limit := 5
		sql_file_path := "static/install/greenbee_admin.sql"
		is_exist := greenbeeUtils.FileExists(sql_file_path)
		if !is_exist {
			c.ErrorJson(0, "sql文件不存在，请重新解压或拉取项目！", 0, show_data)
		}
		split_sqls, err := c.SplitSql(sql_file_path)
		if err != nil {
			c.ErrorJson(0, "sql文件解析失败，请重新解压或拉取项目！", 0, show_data)
		}
		//截取limit 条sql 执行
		sql_key_len := len(split_sqls) - 1
		if n > sql_key_len {
			c.ErrorJson(0, "参数有误！", 0, show_data)
		}
		limit_n := n + limit
		if sql_key_len < limit_n {
			limit_n = sql_key_len
		}

		//连接数据库
		// 数据库连接字符串，格式为：用户名:密码@tcp(地址:端口)/数据库名
		dsn := config_db_username + ":" + config_db_password + "@tcp(" + config_db_host + ":" + config_db_port + ")/" + config_db_name + "?charset=utf8"
		db, err := sql.Open("mysql", dsn)
		if err != nil {
			//log.Fatal(err)
			c.ErrorJson(0, "数据库连接失败:"+err.Error(), 0, show_data)
		}
		defer db.Close()

		//安装完成
		if n >= limit_n {

			//更新数据表数据
			//更新管理员账号信息
			pwd := greenbeeUtils.Sha1(greenbeeUtils.Md5(config_manager_pwd))
			update_sql := ""
			if config_manager == "test" {
				//其他管理员修改为普通管理员
				update_other_sql := "UPDATE go_admin_user SET is_admin = '0' WHERE user_name !='test'"
				_, err := db.Exec(update_other_sql)
				if err != nil {
					//c.ErrorJson(1, "sql执行失败："+sql, 0, show_data)
					c.ErrorJson(0, "安装完成，但管理员信息写入失败！", 0, show_data)
				}
				//test 用户升级成超级管理员
				update_sql = "UPDATE go_admin_user SET is_admin = '1', password = '" + pwd + "' WHERE user_name ='test'"

			} else {
				//更新超级管理员账号信息
				update_sql = "UPDATE go_admin_user SET user_name = '" + config_manager + "', password = '" + pwd + "' WHERE is_admin =1"
			}
			_, err := db.Exec(update_sql)
			if err != nil {
				//c.ErrorJson(1, "sql执行失败："+sql, 0, show_data)
				c.ErrorJson(0, "安装完成，但管理员信息写入失败！", 0, show_data)
			}
			//更新网站配置信息
			update_sql = "UPDATE go_system_setting SET web_name = '" + config_sitename + "',portal_url='" + config_siteurl + "' where types='system'"

			_, err = db.Exec(update_sql)
			if err != nil {
				//c.ErrorJson(1, "sql执行失败："+sql, 0, show_data)
				c.ErrorJson(0, "安装完成，但网站信息写入失败！", 0, show_data)
			}

			//生成文件 install.lock
			if !greenbeeUtils.FileExists(LOCK_FILE_PATH) {
				f, err := os.Create(LOCK_FILE_PATH)
				if err != nil {
					c.ErrorJson(0, "安装完成，但文件写入失败！", 0, show_data)
				}
				f.Close()
			}

			//重新加载orm
			orm.RegisterDataBase("default", "mysql", config_db_username+":"+config_db_password+"@tcp("+config_db_host+":"+config_db_port+")/"+config_db_name+"?charset=utf8&loc=Local")

			c.ErrorJson(2, "安装完成！", 0, show_data)
		}

		//sql执行结果
		var msg_data string
		//循环执行sql语句
		for _, sql := range split_sqls[n:limit_n] {
			_, err := db.Exec(sql)
			//sql 特殊字符转义
			sql = html.EscapeString(sql)
			if err != nil {
				//c.ErrorJson(1, "sql执行失败："+sql, 0, show_data)
				msg_data += "sql执行失败：" + sql + "。<br/>"
			} else {
				msg_data += "sql执行成功：" + sql + "。<br/>"
			}
		}
		c.ErrorJson(1, msg_data, limit_n, show_data)
	}

	c.ErrorJson(0, "操作失败！", 0, data)
}

// 系统安装方法
func (c *CmfInstallController) GetListens() string {

	Licenses := `使用、复制和分发的条款与条件
	
	定义
	
	“许可证” 指本文档第1至第9条所定义的使用、复制和分发的条款与条件。
	
	“许可人” 指授予本许可证的版权所有者或经版权所有者授权的实体。
	
	“法律实体” 指行为实体及其控制、受其控制或与其共同受控的所有其他实体。此处“控制”指：(i) 直接或间接通过合同或其他方式对该实体的管理或决策权；(ii) 拥有该实体50%以上流通股份；或(iii) 对该实体的受益所有权。
	
	“您”（或“您的”） 指根据本许可证行使权利的个人或法律实体。
	
	“源代码形式” 指修改的首选形式，包括但不限于软件源代码、文档源文件和配置文件。
	
	“目标代码形式” 指通过机械转换或翻译源代码形式生成的任何形式，包括但不限于编译后的目标代码、生成的文档以及转换为其他媒体类型的内容。
	
	“作品” 指根据本许可证提供的原创作品（源代码或目标代码形式），以作品包含或附带的版权声明为标识（示例见附录）。
	
	“衍生作品” 指基于原作品修改、注释、扩展或改编后形成的原创作品（源代码或目标代码形式），且整体构成独创性内容。本许可证中，仅与作品接口链接或绑定的独立作品不视为衍生作品。
	
	“贡献” 指版权所有者或其授权方有意提交以纳入作品的内容，包括原作品及其修改或补充。提交形式包括电子邮件列表、代码管理系统等由许可人管理的沟通渠道，但明确标记为“非贡献”的内容除外。
	
	“贡献者” 指许可人及提交贡献并被纳入作品的个人或法律实体。
	
	版权授权
	根据本许可证，每位贡献者特此授予您全球性、永久、非独占、免费、免版税、不可撤销的版权许可，允许您以源代码或目标代码形式复制、修改衍生作品、公开展示、公开执行、分许可及分发原作品及其衍生作品。
	
	专利授权
	根据本许可证，每位贡献者特此授予您全球性、永久、非独占、免费、免版税、不可撤销（除非本节声明终止）的专利许可，允许您制造、使用、销售、进口及转让作品。此许可仅适用于贡献者可授权的、因贡献本身或其与原作品结合必然侵权的专利声明。若您对任何实体发起专利诉讼（包括诉讼中的交叉索赔或反诉），主张作品或贡献构成直接或间接专利侵权，则本许可证授予您的相关专利许可自诉讼提起之日起终止。
	
	再分发
	您可以以任何媒介、无论修改与否、以源代码或目标代码形式再分发作品或其衍生作品，但须满足以下条件：
	
	(a) 向作品或衍生作品的接收者提供本许可证副本；
	
	(b) 修改后的文件需包含显著声明，标明您已更改文件；
	
	(c) 分发衍生作品时，须在源代码中保留原作品的版权、专利、商标及归属声明（无关声明除外）；
	
	(d) 若作品包含“NOTICE”文本文件，则衍生作品的分发须在至少一处（如NOTICE文件、源代码或文档中）包含该文件的可读副本（无关声明除外）。您可添加自己的归属声明，但不得暗示修改许可证。
	
	您可为修改内容添加自己的版权声明，并针对修改部分或衍生作品整体附加不同的授权条款，前提是作品的使用、复制和分发符合本许可证条件。
	
	贡献提交
	除非您明确声明，否则您向许可人提交的任何贡献均默认按本许可证条款授权，不附加额外条件。但本条款不取代您与许可人另行签署的贡献协议。
	
	商标
	本许可证不授予使用许可人商标、服务标记或商品名称的权利，除非用于合理且惯常地描述作品来源或复制NOTICE文件内容。
	
	免责声明
	除非法律要求或书面同意，许可人及贡献者均按“原样”提供作品，不提供任何明示或暗示的担保，包括但不限于适销性、特定用途适用性或非侵权的保证。您须自行承担使用或再分发作品的风险。
	
	责任限制
	任何贡献者均不对您因本许可证或使用作品导致的任何直接、间接、特殊、偶然或后果性损害（包括商誉损失、停工、计算机故障等）承担责任，即使已被告知可能发生此类损害。
	
	担保或额外责任的接受
	您再分发作品时可选择提供支持、担保或赔偿等额外责任服务并收取费用，但须自行承担风险，且须为因此类行为导致的贡献者责任进行赔偿和抗辩。
	
	条款结束
	
	附录：如何将Apache许可证应用于您的作品
	
	要将Apache许可证应用于您的作品，请在作品中附上以下声明模板（替换方括号内内容，保留引号），并按文件格式添加注释。建议在版权声明同一页面包含文件/类名及用途描述，以便第三方识别。
	版权所有 [年份] [版权所有者姓名]  
	
	根据Apache许可证2.0版（“许可证”）授权；  
	除非遵守许可证，否则不得使用此文件。  
	您可在以下网址获取许可证副本：  
	
		http://www.apache.org/licenses/LICENSE-2.0  
	
	除非适用法律要求或书面同意，本软件按“原样”分发，  
	无任何明示或暗示的担保或条件。  
	详见许可证规定的具体语言和限制。  `
	return Licenses
}

// 修改配置文件
func (c *CmfInstallController) ModifyConfig(filePath string, modify_map map[string]string) error {
	file, err := os.OpenFile(filePath, os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var lines []string
	for scanner.Scan() {
		line := scanner.Text()
		for k, v := range modify_map {
			if strings.HasPrefix(line, k+" =") {
				line = k + " = " + v
			}
		}
		// if strings.HasPrefix(line, key+" =") {
		// 	line = key + " = " + value
		// }
		lines = append(lines, line)
	}
	if err := scanner.Err(); err != nil {
		return err
	}

	// 重新写入文件
	file.Truncate(0) // 清空文件内容
	file.Seek(0, 0)  // 重置偏移量到文件开头
	writer := bufio.NewWriter(file)
	for _, line := range lines {
		fmt.Fprintln(writer, line)
	}
	writer.Flush()
	return nil
}

// 分割sql 语句
func (c *CmfInstallController) SplitSql(sql_file string) ([]string, error) {
	var sqlStatements []string
	var isInMultiLineComment bool = false // 用于处理多行注释
	var endOfStatementRegexp = regexp.MustCompile(`;[ \t]*$`)

	// 打开SQL文件
	file, err := os.Open(sql_file)
	if err != nil {
		return sqlStatements, err
	}
	defer file.Close()

	// 创建scanner读取文件内容
	scanner := bufio.NewScanner(file)

	var currentStatement string

	for scanner.Scan() {
		line := scanner.Text()

		// 忽略空行
		if strings.TrimSpace(line) == "" {
			continue
		}

		// 处理单行注释
		if strings.Contains(line, "--") {
			line = line[:strings.Index(line, "--")] // 去掉单行注释部分
		}

		// 处理多行注释
		if strings.Contains(line, "/*") {
			isInMultiLineComment = true
			line = strings.ReplaceAll(line, "/*", "")
		}
		if strings.Contains(line, "*/") {
			isInMultiLineComment = false
			line = strings.ReplaceAll(line, "*/", "")
		}

		if !isInMultiLineComment {
			currentStatement += strings.TrimSpace(line) + " " // 添加当前行到当前语句中，注意添加空格分隔符
		}

		// 检查是否到达了语句的结束（例如分号）或文件末尾，并分割语句
		if endOfStatementRegexp.MatchString(line) { // 检查是否以分号结尾，可能带有空白字符
			sqlStatements = append(sqlStatements, strings.TrimSpace(currentStatement)) // 添加到列表中
			currentStatement = ""                                                      // 重置当前语句变量以开始新的语句收集
		}
	}

	// 检查是否有未完成的 SQL 语句
	if currentStatement != "" {
		sqlStatements = append(sqlStatements, strings.TrimSpace(currentStatement))
	}

	if err := scanner.Err(); err != nil { // 检查scanner的错误（如读取错误）
		return sqlStatements, err
	}

	return sqlStatements, nil
}
